package com.xuanyuanxinyu.core.task;

import android.os.Build;
import android.os.Handler;
import android.os.Looper;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;


import com.xuanyuanxinyu.core.utils.LogUtils;
import com.xuanyuanxinyu.core.utils.LogUtils;

import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class DefaultTaskExecutor extends TaskExecutor {

    private static final int CORE_POOL_SIZE = 4;
    private static final int MAXIMUM_POOL_SIZE = 6;
    private static final int KEEP_ALIVE = 30;

    private final Object mLock = new Object();
    private final Object mSerialLock = new Object();

    private final ScheduledThreadPoolExecutor mScheduledThreadPoolExecutor;
    private final ThreadPoolExecutor mThreadExecutor;
    private final Map<String, SerialExecutor> mSerialExecutorMap = new HashMap<>();
    private final SerialExecutor mDefaultSerialExecutor = new SerialExecutor();

    public DefaultTaskExecutor() {
        int maxThreads = Math.max(Runtime.getRuntime().availableProcessors() - 1, 4);
        mScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(maxThreads, new ThreadFactory() {
            private final AtomicInteger mThreadId = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable runnable) {
                Thread t = new Thread(runnable, "SchExecutor#" + mThreadId.getAndIncrement());
                t.setUncaughtExceptionHandler((t1, e) -> LogUtils.logError(t1.getName() + "--" + e));
                return t;
            }
        });
        mScheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true);
        mScheduledThreadPoolExecutor.setKeepAliveTime(30, TimeUnit.SECONDS);
        mThreadExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, new LinkedBlockingQueue<>(128), new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);

            public Thread newThread(Runnable r) {
                return new Thread(r, "ThrExecutor#" + mCount.getAndIncrement());
            }
        }, new ThreadPoolExecutor.DiscardOldestPolicy());
        mThreadExecutor.allowCoreThreadTimeOut(true);
    }

    @Nullable
    private volatile Handler mMainHandler;

    @Override
    public void schedule(Runnable runnable) {
        mThreadExecutor.execute(runnable);
    }

    @Override
    public void scheduleSerial(Runnable r) {
        mDefaultSerialExecutor.execute(r, false);
    }

    @Override
    public void scheduleSerial(String group, Runnable r) {
        scheduleSerial(group, r, false);
    }

    @Override
    public void scheduleSerial(String group, Runnable r, boolean onlyRecent) {
        SerialExecutor executor;
        synchronized (mSerialLock) {
            executor = mSerialExecutorMap.get(group);
            if (null == executor) {
                executor = new SerialExecutor();
                mSerialExecutorMap.put(group, executor);
            }
        }
        executor.execute(r, onlyRecent);
    }

    @SuppressWarnings("unchecked")
    @Override
    public ScheduledFuture<Object> schedule(Runnable command, long delay, TimeUnit unit) {
        return (ScheduledFuture<Object>) mScheduledThreadPoolExecutor.schedule(command, delay, unit);
    }

    @SuppressWarnings("unchecked")
    @Override
    public ScheduledFuture<Object> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
        return (ScheduledFuture<Object>) mScheduledThreadPoolExecutor.scheduleAtFixedRate(command, initialDelay, period, unit);
    }

    @SuppressWarnings("unchecked")
    @Override
    public ScheduledFuture<Object> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
        return (ScheduledFuture<Object>) mScheduledThreadPoolExecutor.scheduleWithFixedDelay(command, initialDelay, delay, unit);
    }

    @Override
    public void postToMainThread(Runnable runnable) {
        crateMainHandler();
        Objects.requireNonNull(mMainHandler).post(runnable);
    }

    @Override
    public void postToMainThreadDelay(Runnable runnable, long delayTime) {
        crateMainHandler();
        Objects.requireNonNull(mMainHandler).postDelayed(runnable, delayTime);
    }

    @Override
    public void removeRunnable(Runnable runnable) {
        crateMainHandler();
        Objects.requireNonNull(mMainHandler).removeCallbacks(runnable);
    }


    @RequiresApi(api = Build.VERSION_CODES.Q)
    @Override
    public boolean hasCallbacks(Runnable runnable) {
        crateMainHandler();
        if (null == mMainHandler || null == runnable) {
            return false;
        }
        return mMainHandler.hasCallbacks(runnable);
    }

    @Override
    public boolean isMainThread() {
        return Looper.getMainLooper().getThread() == Thread.currentThread();
    }

    private void crateMainHandler() {
        if (mMainHandler == null) {
            synchronized (mLock) {
                if (mMainHandler == null) {
                    mMainHandler = new Handler(Looper.getMainLooper());
                }
            }
        }
    }

    private class SerialExecutor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<>();
        Runnable mActive;

        public synchronized void execute(final Runnable r, boolean onlyRecent) {
            if (onlyRecent) {
                mTasks.clear();
            }
            mTasks.offer(() -> {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                mThreadExecutor.execute(mActive);
            }
        }
    }

}
